package br.com.ir.tcc.geco.service;

import java.io.Serializable;
import java.lang.reflect.Field;
import java.util.Collection;
import java.util.HashMap;
import java.util.List;
import java.util.Map;

import javax.persistence.Id;

import org.apache.commons.beanutils.PropertyUtils;
import org.apache.commons.lang.StringUtils;
import org.hibernate.Criteria;
import org.hibernate.FetchMode;
import org.hibernate.Query;
import org.hibernate.Session;
import org.hibernate.Transaction;
import org.hibernate.criterion.CriteriaSpecification;
import org.hibernate.criterion.Example;
import org.hibernate.criterion.MatchMode;
import org.hibernate.criterion.Order;
import org.hibernate.criterion.Projections;
import org.springframework.context.annotation.Scope;
import org.springframework.stereotype.Component;

@Component
@Scope(value = "prototype")
public class GenericService {
	
	public static final String ID_PROPERTY_DEFAULT_NAME = "id";
	public static final String COUNT = "count";
	public static final String LIST = "list";
	
	
	
	public void save(Object entity){
		
		Object identity = null;
		
		Transaction tx = null;
		
		try {
			
			identity = HibernateSession.createSession();
			Session session = HibernateSession.getSession();
			
			tx = session.getTransaction();
			tx.begin();
			
			Object id =  PropertyUtils.getProperty(entity, "id");
			
			if (id == null)
				session.save(entity);
			 else 
				session.merge(entity);
			
			tx.commit();
			session.flush();
		} catch (Exception e) {
			tx.rollback();
			throw new ServiceException(e);
		} finally {
			closeSession(identity);
		}
		
	}
	
	public void deleteById(String entityClassName, Object id){
		
		Object identity = null;
		
		try {
			
			identity = HibernateSession.createSession();
			Session session = HibernateSession.getSession();
			
			session.delete(entityClassName,id);			
						
		} catch (Exception e) {
			throw new ServiceException(e);
		} finally {
			closeSession(identity);
		}
		
	}
	
	public void delete(Object entity){
		
		Object identity = null;
		
		try {
			
			identity = HibernateSession.createSession();
			Session session = HibernateSession.getSession();
			
			session.delete(entity);
			session.flush();
						
		} catch (Exception e) {
			throw new ServiceException(e);
		} finally {
			closeSession(identity);
		}
		
	}
	
	public Object findById(Object object){
		Object identity = null;
		
		try {
			
			identity = HibernateSession.createSession();
			Session session = HibernateSession.getSession();
			
			return session.get(object.getClass(), (Serializable) getId(object));		
						
		} catch (Exception e) {
			throw new ServiceException(e);
		} finally {
			closeSession(identity);
		}
	}
	
	@SuppressWarnings("unchecked")
	public Object findById(Class entityClass, Serializable id){
		Object identity = null;
		
		try {
			
			identity = HibernateSession.createSession();
			Session session = HibernateSession.getSession();
			
			return session.get(entityClass, id);		
						
		} catch (Exception e) {
			throw new ServiceException(e);
		} finally {
			closeSession(identity);
		}
	}
	
	
	public Object findById(String entityClassName, Serializable id){
		Object identity = null;
		
		try {
			
			identity = HibernateSession.createSession();
			Session session = HibernateSession.getSession();
			
			return session.get(entityClassName, id);		
						
		} catch (Exception e) {
			throw new ServiceException(e);
		} finally {
			closeSession(identity);
		}
	}
	
	@SuppressWarnings("unchecked")
	public Collection findAll(String entityClassName, String... orderByList){
		return findAll(entityClassName, null, orderByList);
	}
	
	@SuppressWarnings("unchecked")
	public Collection findAll(String entityClassName, String[] fetchedProperties, String... orderByList){
		
		Object identity = null;
		
		try {
			
			identity = HibernateSession.createSession();
			Session session = HibernateSession.getSession();
			
			StringBuilder hql = new StringBuilder("FROM entityClassName entity");
			
			if(fetchedProperties != null && fetchedProperties.length > 0)
				for (int i = 0; i < fetchedProperties.length; i++) {
					hql.append("left join fetch entity." + fetchedProperties[i]);
				}
			
			if(orderByList != null && orderByList.length > 0){
				for (int i = 0; i < orderByList.length; i++) {
					hql.append("entity."+orderByList[i]+",");
				}
				hql.deleteCharAt(hql.lastIndexOf(","));
			}	
						
			Query query = session.createQuery(hql.toString().replaceAll("entityClassName", entityClassName));
			
			return query.list();
						
		} catch (Exception e) {
			throw new ServiceException(e);
		} finally {
			closeSession(identity);
		}
		
		
	}
	
	public List findAll(Object entity, String[] fetchList, String... orderBylist){
		
		Object identity = null;
		
		try {
			
			identity = HibernateSession.createSession();
			Session session = HibernateSession.getSession();
			
			Example example = Example.create(entity)
										.ignoreCase()
										.enableLike(MatchMode.ANYWHERE);
										
			Criteria criteria = session.createCriteria(entity.getClass())
										.add(example);
			
			if(fetchList != null){
				for (String fetchProperty : fetchList) {					
					criteria.createAlias(fetchProperty, 
							fetchProperty.lastIndexOf(".") >= 0 
								? fetchProperty.substring(fetchProperty.lastIndexOf("."))
								: fetchProperty,
							CriteriaSpecification.LEFT_JOIN);
					criteria.setFetchMode(fetchProperty, FetchMode.JOIN);
				}
			}
			
			if(orderBylist != null){
				for (String order : orderBylist) {
					if (order != null) {
						Order orderBy = order.indexOf(" desc") >= 0 
						? Order.desc(order.replaceAll(" desc", ""))
						: Order.asc(order.replaceAll(" asc", ""));
						criteria.addOrder(orderBy);
					}			
				}
			}
			
			return criteria.list();
			
		} catch (Exception e) {
			throw new ServiceException(e);
		} finally {
			closeSession(identity);
		}
		
	}
	
	@SuppressWarnings("unchecked")
	public Map<String,Object> findAll(Object entity, Integer startIndex, Integer rowsPerPage, String[] fetchList, String... orderBylist){

		Object identity = null;
		
		try {
			
			Map<String, Object> map = new HashMap<String, Object>();
			
			identity = HibernateSession.createSession();
			Session session = HibernateSession.getSession();
			
			Example example = Example.create(entity)
										.ignoreCase()
										.enableLike(MatchMode.ANYWHERE);
			
			Integer count = (Integer) session.createCriteria(entity.getClass())
										.add(example)
										.setProjection(Projections.projectionList()
												.add(Projections.rowCount())
										).uniqueResult();
										
			Criteria criteria = session.createCriteria(entity.getClass())
										.add(example)
										.setFirstResult(startIndex)
										.setMaxResults(rowsPerPage);
			
			if(fetchList != null){
				for (String fetchProperty : fetchList) {					
					criteria.createAlias(fetchProperty, 
							fetchProperty.lastIndexOf(".") >= 0 
								? fetchProperty.substring(fetchProperty.lastIndexOf("."))
								: fetchProperty,
							CriteriaSpecification.LEFT_JOIN);
					criteria.setFetchMode(fetchProperty, FetchMode.JOIN);
				}
			}
			
			if(orderBylist != null){
				for (String order : orderBylist) {
					if (order != null) {
						Order orderBy = order.indexOf(" desc") >= 0 
						? Order.desc(order.replaceAll(" desc", ""))
						: Order.asc(order.replaceAll(" asc", ""));
						criteria.addOrder(orderBy);
					}			
				}
			}
			
			map.put(COUNT, count);
			map.put(LIST, criteria.list());
			
			return map;
		} catch (Exception e) {
			throw new ServiceException(e);
		} finally {
			closeSession(identity);
		}
		
		
	}
	
	protected Query setProperties(Query query, List parameters){
		for (int i = 0; i < parameters.size(); i++) 
			query.setParameter(i, parameters.get(i));
		return query;
	}
	
	@SuppressWarnings({ "unchecked" })
	protected void appendFilter(List parameters, StringBuilder hql, String filter, Object value){
		if(value == null
				|| (value instanceof String && StringUtils.isEmpty((String) value))
				|| (value instanceof Collection && ((Collection)value).isEmpty()))
			return;
		hql.append(filter);
		parameters.add(value);
	}
	
	protected void closeSession(Object identity){
		try {
			HibernateSession.closeSession(identity);
		} catch (Exception e) {
			throw new ServiceException(e);
		}
	}
	
	protected Object getId(Object object) throws Exception{
		for(Field field :object.getClass().getFields())
			if(field.isAnnotationPresent(Id.class))
				return field.get(object);		
		return null;
	}

}
